﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using eProject.Model;

namespace eProject.Logic
{
    public class Activities
    {
        private readonly Dictionary<Activity, double> _activities = new Dictionary<Activity, double>();

        public long Count { get { return _activities.Count; } }

        private static Activities _instance;
        public static Activities Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new Activities();
                }
                return _instance;
            }
        }

        #region Events
        public delegate void ActivityEventHandler(Activity activity);

        public event ActivityEventHandler ActivityAdded;

        private void InvokeActivityAdded(Activity activity)
        {
            var handler = ActivityAdded;
            if (handler != null) handler(activity);
        }

        public event ActivityEventHandler ActivityRemoved;

        private void InvokeActivityRemoved(Activity activity)
        {
            var removed = ActivityRemoved;
            if (removed != null) removed(activity);
        }

        public event ActivityEventHandler ActivityChanged;

        private void InvokeActivityChanged(Activity activity)
        {
            var changed = ActivityChanged;
            if (changed != null) changed(activity);
        }

        public delegate void ManHoursChangedEventHandler(IEnumerable<Activity> activities);

        public event ManHoursChangedEventHandler ManHoursChanged;

        private void InvokeManHoursChanged(IEnumerable<Activity> activities)
        {
            var changed = ManHoursChanged;
            if (changed != null) changed(activities);
        }
        #endregion
        //TODO ^^^               

        public IEnumerable<Activity> GetActivities(Employee employee)
        {
            var activities = from act in _activities.Keys
                             where act.Employee == employee
                             select act;
            return activities.ToList();
        }

        public IEnumerable<Activity> GetActivities(Task task)
        {
            var activities = from act in _activities.Keys
                             where act.Task == task
                             select act;
            return activities.ToList();           
        }   
     
        public IEnumerable<Activity> GetActivities()
        {
            return _activities.Keys;//TODO checkit
        }  

        //public IEnumerable<Activity> GetActivities(Task task, Period period)
        //{
        //    return GetByPeriod(GetActivities(task), period);
        //}

        //public IEnumerable<Activity> GetActivities(Employee employee, Period period)
        //{
        //    return GetByPeriod(GetActivities(employee), period);
        //}
      
        public double GetManHours(Activity activity)
        {
            return _activities[activity];//TODO Cheking if if _activities doesn't contain the activity
        }

        public double GetManHours(Task task)
        {
            var acts = GetActivities(task);
            double sum = 0;
            foreach (var act in acts)
            {
                sum += GetManHours(act);
            }
            return sum;
        }

        //private Activities()
        //{
        //    //Do nothing
        //}
        public void AddActivity(Activity activity)
        {
            _activities.Add(activity, 0);
            activity.PeriodChanged += OnActivityPeriodChanged;
            activity.PriorityChanged += OnActivityPriorityChanged;
            RecalculateByActivity(activity);
            InvokeActivityAdded(activity);
        }

        public void RemoveActivity(Activity activity)
        {
            _activities.Remove(activity);
            activity.PeriodChanged -= OnActivityPeriodChanged;
            activity.PriorityChanged -= OnActivityPriorityChanged;
            RecalculateByActivity(activity);
            InvokeActivityRemoved(activity);
        }

        public void AddActivities(IEnumerable<Activity> activities)
        {
            foreach (var activity in activities)
            {
                AddActivity(activity);//TODO replace this by correct solution
            }
        }

        private void OnActivityPeriodChanged(Activity activity, Period oldValue, Period newValue)
        {
            var begin = oldValue.Begin < newValue.Begin ? oldValue.Begin : newValue.Begin;
            var end = oldValue.End > newValue.End ? oldValue.End : newValue.End;
            var period = new Period(begin, end);
            RecalculateByEmployeeAndPeriod(activity.Employee, period);
            InvokeActivityChanged(activity);
        }

        private void OnActivityPriorityChanged(Activity activity)
        {
            RecalculateByActivity(activity);
            InvokeActivityChanged(activity);
        }

        private static IEnumerable<Activity> GetByPeriod(IEnumerable<Activity> activities, Period period)
        {
            var result = from act in activities
                         where act.Period.Begin < period.End && act.Period.End > period.Begin
                         select act;
            return result.ToList();
        }       

        private static Period CalculatePeriod(IEnumerable<Activity> activities, Period period)
        {
            var acts = from act in activities
                       where period.Begin < act.Period.End && period.Begin > act.Period.Begin
                       select act;
            if (acts.Count() > 0)
            {
                var newBegin = acts.Min(a => a.Period.Begin);
                return CalculatePeriod(activities, new Period(newBegin, period.End));
            }

            acts = from act in activities
                   where period.End > act.Period.Begin && period.End < act.Period.End
                   select act;
            if (acts.Count() > 0)
            {
                var newEnd = acts.Max(a => a.Period.End);
                return CalculatePeriod(activities, new Period(period.Begin, newEnd));
            }
            return period;
        }        

        private static IList<Period> GetIntersections(IEnumerable<Activity> activities)
        {
            var intscs = (from act in activities
                          select act.Period.Begin).Union(from act in activities
                                                         select act.Period.End).Distinct().OrderBy(d => d.Ticks).ToList();
            var periods = new List<Period>(intscs.Count-1);
            for (var i = 0; i < intscs.Count-1; i++)
            {
                var p = new Period(intscs[i], intscs[i + 1]);
                periods.Add(p);
            }
            return periods;
        }

        private void RecalculateActivities(IEnumerable<Activity> activities)
        {

            foreach (var activity in activities)
            {
                _activities[activity] = 0;
            }

            var intersections = GetIntersections(activities);           

            foreach (var period in intersections)
            {
                var acts = GetByPeriod(activities, period);
                var sum = acts.Sum(a => a.Priority);

                foreach (var act in acts)
                {
                    _activities[act] += (((double) act.Priority)/sum)*act.Employee.GetManHours(period);//TODO replace this 
                }
            }
            InvokeManHoursChanged(activities);
        }

        private void RecalculateByActivity(Activity activity)
        {
            RecalculateByEmployeeAndPeriod(activity.Employee,activity.Period);
        }

        private void RecalculateByEmployeeAndPeriod(Employee employee, Period period)
        {
            var acts = GetActivities(employee);
            var p = CalculatePeriod(acts, period);
            var result = GetByPeriod(acts, p);
            RecalculateActivities(result);
        }      

    }
}
